package com.bot.backend.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bot.backend.consumer.WebSocketServer;
import com.bot.backend.pojo.Record;
import com.bot.backend.pojo.Snake;
import com.bot.backend.pojo.User;
import lombok.Data;
import org.springframework.security.core.parameters.P;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;

public class Game extends Thread { // 启动多线程
    private Integer rows;
    private Integer cols;
    private Integer inner_walls_count;
    Integer [][] g;

    final private static int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};

    // 两名玩家的下一步操作
    private Integer nextStepA = null;
    private Integer nextStepB = null;

    private Player playerA, playerB; // 玩家A和玩家B

    // 哪条蛇输了, （all：平局，A：A输， B：B输）
    private String loser = "";

    private final static String addSnakeUrl = "http://127.0.0.1:3002/snake/add/";

    public Player getPlayerA() {
        return this.playerA;
    }

    public Player getPlayerB() {
        return this.playerB;
    }

    public void setNextStepA(Integer nextStepA) {
        lock.lock(); // 防止读写冲突
        try {
            this.nextStepA = nextStepA;
        } finally {
            lock.unlock();
        }

    }

    public void setNextStepB(Integer nextStepB) {
        lock.lock();
        try {
            this.nextStepB = nextStepB;
        } finally {
            lock.unlock();
        }
    }

    // 防止客户端输入操作和服务端判断是否输入操作产生读写冲突
    private ReentrantLock lock = new ReentrantLock();

    // 游戏状态 playing 正在进行中，finished表示本局游戏完成
    private String status = "playing";

    public Game(Integer rows,
                Integer cols,
                Integer inner_walls_count,
                Integer idA,
                Integer idB,
                Snake snakeA,
                Snake snakeB) {
        this.rows = rows;
        this.cols = cols;
        this.inner_walls_count = inner_walls_count;
        this.g = new Integer[rows][cols];

        Integer snakeIdA = -1, snakeIdB = -1;
        String snakeCodeA = "", snakeCodeB = "";
        if (snakeA != null) {
            snakeIdA = snakeA.getId();
            snakeCodeA = snakeA.getContent();
        }
        if (snakeB != null) {
            snakeIdB = snakeB.getId();
            snakeCodeB = snakeB.getContent();
        }

        // 初始化游戏开始玩家的坐标
        this.playerA = new Player(idA, snakeIdA, snakeCodeA, this.rows - 2, 1, new ArrayList<>());
        this.playerB = new Player(idB, snakeIdB, snakeCodeB, 1, this.cols - 2, new ArrayList<>());
    }

    public Integer[][] getG() { // 返回生成的地图
        return g;
    }

    private boolean draw() { // 生成地图
        for (int i = 0; i < this.rows; i ++ ) { // 初始化地图
            for (int j = 0; j < this.cols; j ++ )
                g[i][j] = 0;
        }

        // 给四周加墙
        for (int r = 0; r < this.rows; r ++) {
            this.g[r][0] = g[r][this.cols - 1] = 1;
        }

        for (int c = 0; c < this.cols; c ++ ) {
            this.g[0][c] = g[this.rows - 1][c] = 1;
        }

        // 创建随机障碍物
        Random random = new Random();
        for (int i = 0; i < this.inner_walls_count / 2; i ++ ) {
            for (int j = 0; j < 1000; j ++ ) {
                Integer r = random.nextInt(this.rows); // 返回0到this.rows - 1中的一个随机值
                Integer c = random.nextInt(this.cols);
                if (this.g[r][c] == 1 || this.g[this.rows - 1 - r][this.cols - 1 - c] == 1) continue;
                if (r == this.rows - 2 && c == 1 || r == 1 && c == this.cols - 2) continue;

                g[r][c] = g[this.rows - r - 1][this.cols - c - 1] = 1;
                break;
            }
        }

        // 判断地图是否连通
        return check_connectivity(this.rows - 2, 1, 1, this.cols - 2);
    }

    private boolean check_connectivity(Integer sx, Integer sy, Integer tx, Integer ty) {
        if (sx == tx && sy == ty) return true;
        g[sx][sy] = 1;
        for (int i = 0; i < 4; i ++ ) {
            int x = sx + dx[i], y = sy + dy[i];
            if (x < 0 || x >= this.rows || y < 0 || y >= this.cols) continue;
            if (g[x][y] == 0 && this.check_connectivity(x, y, tx, ty)) {
                g[sx][sy] = 0;
                return true;
            }
        }

        g[sx][sy] = 0;
        return false;
    }

    public void createMap() {
        for (int i = 0; i < 1000; i ++ ) {
            if (this.draw()) {
                break;
            }
        }
    }

    private String getInput(Player player) { //将当前局面信息编码成字符串
        // 地图#my.sx#my.xy#我的操作#oppo,sx#oppo.xy#对手的操作
         Player me, you;
         if (playerA.getId().equals(player.getId())) {
             me = playerA;
             you = playerB;
         } else {
             me = playerB;
             you = playerA;
         }

         return getMapString() + "#" +
         me.getSx() + "#" +
         me.getSy() + "#(" + // 加括号是防止为空
         me.getStringByListSteps() + ")#" +
         you.getSx() + "#" +
         you.getSy() + "#(" +
         you.getStringByListSteps() + ")#";
    }

    private void sendSnakeCode(Player player) {
        if (player.getSnakeId().equals(-1)) return ; // 亲自出马, 不需要执行代码
        MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
        data.add("user_id", player.getId().toString());
        data.add("snake_code", player.getSnakeCode());
        data.add("input", this.getInput(player));

        WebSocketServer.restTemplate.postForObject(addSnakeUrl, data, String.class);
    }

    private boolean nestStep() { // 等待两名玩家的下一步操作
        try {
            /*每200ms走一个格子，因此如果用户输入的较快
            在行动的200ms内输入操作则会遗漏一些步数因此需要睡眠200ms*/
            Thread.sleep(200);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        this.sendSnakeCode(playerA);
        this.sendSnakeCode(playerB);

        for (int i = 0; i < 50; i ++ ) {
            try {
                Thread.sleep(100);
                lock.lock();
                // 若在5秒内两名玩家输入了操作
                if (this.nextStepA != null && this.nextStepB != null) {
                    playerA.getSteps().add(this.nextStepA);
                    playerB.getSteps().add(this.nextStepB);
                    return true;
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
        }

        return false;
    }

    // 判断蛇A是否撞墙
    private boolean check_valid(List<Cell> cellsA, List<Cell> cellsB) {
        int len = cellsA.size(); // 两蛇一样长
        Cell cell = cellsA.get(len - 1);
        // 判断蛇头A是否撞墙
        if (this.g[cell.x][cell.y] == 1) return false;

        // 判断蛇A是否碰到自己身体
        for (int i = 0; i < len - 1; i ++ ) {
            if (cellsA.get(i).x == cell.x && cellsA.get(i).y == cell.y) {
                return false;
            }
        }

        for (int i = 0; i < len - 1; i ++ ) {
            if (cellsB.get(i).x == cell.x && cellsB.get(i).y == cell.y) {
                return false;
            }
        }

        return true;
    }

    // 判断两名玩家下一步操作是否合法
    private void judge() {
        List<Cell> cellsA = playerA.getCells();
        List<Cell> cellsB = playerB.getCells();

        boolean validA = this.check_valid(cellsA, cellsB);
        boolean validB = this.check_valid(cellsB, cellsA);

        if (!validA || !validB) {
            status = "finished";

            if (!validA && !validB) {
                loser = "all";
            } else if (!validA) {
                loser = "A";
            } else if (!validB) {
                loser = "B";
            }
        }
    }

    private String getMapString() {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < this.rows; i ++ ) {
            for (int j = 0; j < this.cols; j ++ ) {
                stringBuilder.append(g[i][j]);
            }
        }

        return stringBuilder.toString();
    }

    private void updateUserRating(Player player, Integer rating) {
        User user = WebSocketServer.userMapper.selectById(player.getId());
        user.setRating(rating);
        WebSocketServer.userMapper.updateById(user);
    }

    private void saveToDataBase() {
        Integer ratingA = WebSocketServer.userMapper.selectById(this.playerA.getId()).getRating();
        Integer ratingB = WebSocketServer.userMapper.selectById(this.playerB.getId()).getRating();

        if ("A".equals(this.loser)) {
            ratingA -= 2;
            ratingB += 5;
        } else if ("B".equals(this.loser)) {
            ratingA += 5;
            ratingB -= 2;
        }

        this.updateUserRating(playerA, ratingA);
        this.updateUserRating(playerB, ratingB);

        Record record = new Record();
        record.setAId(playerA.getId());
        record.setASx(playerA.getSx());
        record.setASy(playerA.getSy());
        record.setBId(playerB.getId());
        record.setBSx(playerB.getSx());
        record.setBSy(playerB.getSy());
        record.setASteps(playerA.getStringByListSteps());
        record.setBSteps(playerB.getStringByListSteps());
        record.setMap(getMapString());
        record.setLoser(this.loser);
        record.setCreateTime(new Date());
        WebSocketServer.recordMapper.insert(record);
    }

    // 向两名玩家公布结果
    private void sendResult() {
        JSONObject resp = new JSONObject();
        resp.put("event", "result");
        resp.put("loser", this.loser);
        this.saveToDataBase();
        sendAllMessage(resp.toJSONString());
    }

    // 向两名玩家传递移动信息
    private void sendMove() {
        // 涉及下一步操作需要加锁
        lock.lock();
        try {
            JSONObject resp = new JSONObject();
            resp.put("event", "move");
            resp.put("a_direction", this.nextStepA);
            resp.put("b_direction", this.nextStepB);
            sendAllMessage(resp.toJSONString());
            // 进行下一步
            this.nextStepA = this.nextStepB = null;
        } finally {
            lock.unlock();
        }

    }

    // 向两名玩家发送信息
    private void sendAllMessage(String message) {
        if (WebSocketServer.users.get(this.playerA.getId()) != null) {
            WebSocketServer.users.get(this.playerA.getId()).sendMessage(message);
        }

        if (WebSocketServer.users.get(this.playerB.getId()) != null) {
            WebSocketServer.users.get(this.playerB.getId()).sendMessage(message);
        }

    }

    @Override
    public void run() { // 每开启一个新线程后，run函数会作为新线程的入口函数
        for (int i = 0; i < 2000; i ++ ) {
            if (nestStep()) { // 是否获取两条蛇的下一步操作
                this.judge();
                // 若该步操作成功，将此消息发送回两名玩家
                if (status.equals("playing")) {
                    this.sendMove();
                } else {
                    this.sendResult();
                    break;
                }
            } else {
                this.status = "finished";
                // 防止在执行这块代码途中突然读到操作
                lock.lock();
                try {
                    if (this.nextStepA == null && this.nextStepB == null) {
                        this.loser = "all";
                    } else if (this.nextStepA == null) {
                        this.loser = "A";
                    } else if (this.nextStepB == null) {
                        this.loser = "B";
                    }
                } finally {
                    lock.unlock();
                }
                sendResult();
                break;
            }
        }
    }
}
